// SPDX-License-Identifier: GPL-2.0
/* smc_app.c - the command application for SMC
 *
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <mach/debug.h>
#include "smc.h"
#include "smc_app.h"

static struct smc_rx rx;

//static __attribute((aligned(0x1000))) struct smc_apdu_command command;
//static __attribute((aligned(0x1000))) struct smc_apdu_response response;

static char *hextochar = "0123456789abcdef";


int smc_handle(struct smc_apdu_command *command,
		struct smc_apdu_response *response)
{
	return 0;
}

/* judge sim response, correct sw is "90 00", others is wrong */
int judge_response_sw(struct smc_apdu_response response, int cnt)
{
	if ((response.data[cnt-2] != 0x90) || (response.data[cnt-1] != 0)) {
		PRT_INFO("response status error!\n");
		PRT_INFO("response sw is %x %x!\n", response.data[cnt-2],
			response.data[cnt-1]);
		return ERROR;
	}

	return response.data[cnt-1];
}

/* judge sim response ins, correct case is  received ins equal command ins */
int judge_cmd_status(u8 rx, u8 cmd)
{
	if (rx != cmd) {
		PRT_INFO("command status error!\n");
		return ERROR;
	}

	return 0;
}

/* judge sim response about selecting DF or EF ,fisrt byte is '9F',
 * second byte depend on different files.
 */
int judge_select_sw(struct smc_apdu_response response, int cnt)
{
	if (response.data[cnt-2] != 0x9F) {
		PRT_INFO("select status error!\n");
		PRT_INFO("select sw is %x %x!\n", response.data[cnt-2],
			response.data[cnt-1]);
		return ERROR;
	}

	return 0;
}

/* print binary as a string. */
int print_binary(u8 *data, u32 cnt)
{
	int i, j;
	u8 temp[MAX_LEN];

	j = 0;
	for (i = 1; i < cnt-2; i++) {
		temp[j] = hextochar[(data[i] & 0xf0)>>4];
		temp[j+1] = hextochar[(data[i] & 0xf)];
		j += 2;
	}
	PRT_INFO("data is %s\n", temp);

	return 0;
}

/* print record as a string. */
int print_record(u8 *data, u32 cnt)
{
	int i, j;
	u8 temp[MAX_LEN * 2];

	j = 0;
	for (i = 1; i < cnt-2; i++) {
		temp[j] = hextochar[(data[i] & 0xf0)>>4];
		temp[j+1] = hextochar[(data[i] & 0xf)];
		j += 2;
	}
	PRT_INFO("data is %s\n", temp);

	return 0;
}

/* print contact as a string. */
int print_contact(u8 *data, u32 cnt)
{
	int i, j;
	struct contact temp;

	if (data[0] == 0xff) {
		PRT_INFO("contact is empty\n");
		return 0;
	}
	for (i = 1; i < 15; i++) {
		if (data[i] != 0xff)
			temp.name[i-1] = data[i];
	}
	j = 0;
	for (i = 17; i < 27; i++) {
		if (data[i] != 0xff) {
			temp.telephone[j] = (data[i] & 0xf) + '0';
			if (j >= 10) {
				temp.telephone[j+1] = '\0';
				break;
			}
			temp.telephone[j+1] = ((data[i] & 0xf0) >> 4) + '0';
		}
		j += 2;
	}

	PRT_INFO("contact name is %s\n", temp.name);
	PRT_INFO("contact telephone is %s\n", temp.telephone);

	return 0;
}

/* print message as a string. */
int print_message(u8 *data, u32 cnt)
{
	int i;
	u8 temp[MAX_LEN];
	int j = 0;

	if (data[0] == 0xff) {
		PRT_INFO("message is empty\n");
		return 0;
	}

	for (i = 2; i < cnt-2; i++) {
		if (data[i] == 0 || data[i] == 0xff)
			break;
		temp[j] = data[i];
		j++;
	}
	temp[j] = '\0';
	PRT_INFO("data is %s\n", temp);
	return 0;
}

/* print real message as a string, phone, time and message. */
int print_real_message(u8 *data, u32 cnt)
{
	int i;
	int j = 0;
	u8 phone[14];
	u8 time[14];
	u8 message[100];

	for (i = 12; i < 19; i++) {
		if (data[i] != 0xff) {
			phone[j] = hextochar[(data[i] & 0xf)];
			if (j >= 12)
				break;
			phone[j+1] = hextochar[((data[i] & 0xf0) >> 4)];
		}
		j += 2;
	}
	for (i = 21; i < 28; i++) {
		if (data[i] != 0xff) {
			time[j] = hextochar[(data[i] & 0xf)];
			if (j >= 12)
				break;
			time[j+1] = hextochar[((data[i] & 0xf0) >> 4)];
		}
		j += 2;
	}
	for (i = 29; i < 29 + data[28]; i++)
		message[j] = data[i];


	PRT_INFO("phone is %s\n", phone);
	PRT_INFO("time is 20%c%c/%c%c/%c%c %c%c:%c%c:%c%c:\n", time[0], time[1],
		time[2], time[3], time[4], time[5], time[6], time[7], time[8],
		time[9], time[10], time[11]);
	PRT_INFO("message is %s\n", message);
	return 0;
}

/* select file command, file_id is two bytes. */
int smc_cmd_select_file(u32 file_id)
{
	struct smc_apdu_command command;
	struct smc_apdu_response response;
	u32 cmd_len;
	u64 cmd_addr, rsp_addr;
	int ret = 0;

	command.header.cla = SIM_NORMAL_CLASS;
	command.header.ins = SIM_CMD_SELECT_INST;
	command.header.p1 = 0;
	command.header.p2 = 0;
	command.header.p3 = SIM_CMD_SELECT_DATA_LEN;

	command.body.data[1] = (file_id & 0xff);
	command.body.data[0] = (file_id & 0xff00) >> 8;

	cmd_addr = (size_t)(&(command.header));
	rsp_addr = (size_t)(&response);
	cmd_len = SIM_CMD_SELECT_HEADER_LEN;
	ret = smc_t0_data_exchange(cmd_addr, cmd_len, rsp_addr, &rx);
	if (ret < 0) {
		PRT_INFO("smc data exchange failed\n");
		return ERROR;
	}
	judge_cmd_status(response.data[0], SIM_CMD_SELECT_INST);

	cmd_addr = (size_t)(&(command.body));
	rsp_addr = (size_t)(&(response));
	cmd_len = SIM_CMD_SELECT_BODY_LEN;
	ret = smc_t0_data_exchange(cmd_addr, cmd_len, rsp_addr, &rx);
	if (ret < 0) {
		PRT_INFO("smc data exchange failed\n");
		return ERROR;
	}
	ret = judge_select_sw(response, rx.rx_cnt);

	return ret;
}

/* after specific operations, reset directory to MF. */
int smc_reset_select_directory(void)
{
	int ret;

	PRT_INFO("---reset directory to MF---\n");
	ret = smc_cmd_select_file(SIM_FILE_ID_MF);
	if (ret < 0)
		PRT_INFO("smc reset directory failed\n");
	return 0;
}
EXPORT_SYMBOL_GPL(smc_reset_select_directory);

/* get response command to get EF/DF information */
struct smc_apdu_response sim_cmd_get_response(u32 len)
{
	struct smc_apdu_command command;
	struct smc_apdu_response response;
	u32 cmd_len;
	u64 cmd_addr, rsp_addr;
	int ret = 0;

	command.header.cla = SIM_NORMAL_CLASS;
	command.header.ins = SIM_CMD_GET_RESPONSE_INST;
	command.header.p1 = 0x00;
	command.header.p2 = 0x00;
	command.header.p3 = len;

	cmd_addr = (size_t)(&(command.header));
	rsp_addr = (size_t)(&response);
	cmd_len = SIM_CMD_SELECT_HEADER_LEN;
	ret = smc_t0_data_exchange(cmd_addr, cmd_len, rsp_addr, &rx);
	if (ret < 0) {
		PRT_INFO("smc data exchange failed\n");
		return response;
	}
	judge_cmd_status(response.data[0], SIM_CMD_GET_RESPONSE_INST);
	judge_response_sw(response, rx.rx_cnt);

	return response;
}

/* return information concerning the current directory. */
int smc_cmd_get_status(u32 smc_index)
{
	struct smc_apdu_command command;
	struct smc_apdu_response response;
	u32 cmd_len;
	u64 cmd_addr, rsp_addr;
	int ret = 0;

	command.header.cla = SIM_NORMAL_CLASS;
	command.header.ins = SIM_CMD_STATUS_INST;
	command.header.p1 = 0;
	command.header.p2 = 0;
	command.header.p3 = SIM_CMD_STATUS_DATA_LEN;

	cmd_addr = (size_t)(&(command.header));
	rsp_addr = (size_t)(&(response));
	cmd_len = SIM_CMD_STATUS_FRAME_LEN;

	ret = smc_t0_data_exchange(cmd_addr, cmd_len, rsp_addr, &rx);
	if (ret < 0) {
		PRT_INFO("smc data exchange failed\n");
		return ERROR;
	}
	judge_response_sw(response, rx.rx_cnt);
	return 0;
}

/* read a string of bytes from the current transparent EF */
int smc_cmd_read_binary(u32 len)
{
	struct smc_apdu_command command;
	struct smc_apdu_response response;
	u32 cmd_len;
	u64 cmd_addr, rsp_addr;
	int ret = 0;

	command.header.cla = SIM_NORMAL_CLASS;
	command.header.ins = SIM_CMD_READ_BINARY_INST;
	command.header.p1 = 0;
	command.header.p2 = 0;
	command.header.p3 = len;

	cmd_addr = (size_t)(&(command.header));
	rsp_addr = (size_t)(&(response));
	cmd_len = SIM_CMD_READ_BINARY_FRAME_LEN;

	ret = smc_t0_data_exchange(cmd_addr, cmd_len, rsp_addr, &rx);
	if (ret < 0) {
		PRT_INFO("smc data exchange failed\n");
		return ERROR;
	}
	ret = judge_cmd_status(response.data[0], SIM_CMD_READ_BINARY_INST);
	if (ret < 0)
		return -1;
	ret = judge_response_sw(response, rx.rx_cnt);
	if (ret < 0)
		return -1;

	print_binary(response.data, rx.rx_cnt);

	return 0;
}

/* updates the current transparent EF with a string of bytes */
int smc_cmd_update_binary(u8 *data, u32 len)
{
	struct smc_apdu_command command;
	struct smc_apdu_response response;
	u32 cmd_len;
	u64 cmd_addr, rsp_addr;
	int ret = 0;
	int i;

	command.header.cla = SIM_NORMAL_CLASS;
	command.header.ins = SIM_CMD_UPDATE_BINARY_INST;
	command.header.p1 = 0;
	command.header.p2 = 0;
	command.header.p3 = len;

	cmd_addr = (size_t)(&(command.header));
	rsp_addr = (size_t)(&(response));
	cmd_len = SIM_CMD_READ_BINARY_FRAME_LEN;

	ret = smc_t0_data_exchange(cmd_addr, cmd_len, rsp_addr, &rx);
	if (ret < 0) {
		PRT_INFO("smc data exchange failed\n");
		return ERROR;
	}
	ret = judge_cmd_status(response.data[0],
				   SIM_CMD_UPDATE_BINARY_INST);
	if (ret < 0)
		return -1;

	cmd_len = len;
	for (i = 0; i < cmd_len; i++)
		command.body.data[i] = data[i];
	cmd_addr = (size_t)(&(command.body));
	ret = smc_t0_data_exchange(cmd_addr, cmd_len, rsp_addr, &rx);
	if (ret < 0) {
		PRT_INFO("smc data exchange failed\n");
		return ERROR;
	}
	ret = judge_response_sw(response, rx.rx_cnt);
	if (ret < 0)
		return -1;

	return 0;
}

/* reads one complete record in the current linear fixed or cyclic EF */
int smc_cmd_read_record(u8 data[MAX_LEN], u32 len, u32 mode, u32 id)
{
	struct smc_apdu_command command;
	struct smc_apdu_response response;
	u32 cmd_len;
	u64 cmd_addr, rsp_addr;
	u32 ret = 0;

	command.header.cla = SIM_NORMAL_CLASS;
	command.header.ins = SIM_CMD_READ_RECORD_INST;
	command.header.p1 = id;
	command.header.p2 = mode;
	command.header.p3 = len;

	cmd_addr = (size_t)(&(command.header));
	rsp_addr = (size_t)(&(response));
	cmd_len = SIM_CMD_READ_RECORD_FRAME_LEN;

	ret = smc_t0_data_exchange(cmd_addr, cmd_len, rsp_addr, &rx);
	if (ret < 0) {
		PRT_INFO("smc data exchange failed\n");
		return ERROR;
	}
	ret = judge_cmd_status(response.data[0], SIM_CMD_READ_RECORD_INST);
	if (ret < 0)
		return -1;
	ret = judge_response_sw(response, rx.rx_cnt);
	if (ret < 0)
		return -1;

	print_record(response.data, rx.rx_cnt);
	memcpy(data, response.data, len);
	return rx.rx_cnt;
}

/* updates one complete record in the current linear fixed or cyclic EF */
int smc_cmd_update_record(u8 *data, u32 len, u32 mode, u32 id)
{
	struct smc_apdu_command command;
	struct smc_apdu_response response;
	u32 cmd_len;
	u64 cmd_addr, rsp_addr;
	u32 ret = 0;
	int i;

	command.header.cla = SIM_NORMAL_CLASS;
	command.header.ins = SIM_CMD_UPDATE_RECORD_INST;
	command.header.p1 = id;
	command.header.p2 = mode;
	command.header.p3 = len;

	cmd_addr = (size_t)(&(command.header));
	rsp_addr = (size_t)(&(response));
	cmd_len = SIM_CMD_UPDATE_RECORD_FRAME_LEN;

	ret = smc_t0_data_exchange(cmd_addr, cmd_len, rsp_addr, &rx);
	if (ret < 0) {
		PRT_INFO("smc data exchange failed\n");
		return ERROR;
	}
	ret = judge_cmd_status(response.data[0], SIM_CMD_UPDATE_RECORD_INST);
	if (ret < 0)
		return -1;

	cmd_len = len;
	for (i = 0; i < len; i++)
		command.body.data[i] = data[i];
	cmd_addr = (size_t)(&(command.body));
	rsp_addr = (size_t)(&(response));
	ret = smc_t0_data_exchange(cmd_addr, cmd_len, rsp_addr, &rx);
	if (ret < 0) {
		PRT_INFO("smc data exchange failed\n");
		return ERROR;
	}

	ret = judge_response_sw(response, rx.rx_cnt);
	if (ret < 0)
		return -1;

	return 0;
}

/* get binary from current transparent EF. */
int smc_get_binary(u32 file_df_id, u32 file_ef_id)
{
	u32 len;
	u32 sw2;
	struct smc_apdu_response response;

	PRT_INFO("start smc get binary\n");

	sw2 = smc_cmd_select_file(file_df_id);
	if (sw2 < 0)
		goto fail;
	sw2 = smc_cmd_select_file(file_ef_id);
	if (sw2 < 0)
		goto fail;
	response = sim_cmd_get_response(sw2);
	len = response.data[3]*16 + response.data[4];
	sw2 = smc_cmd_read_binary(len);
	if (sw2 < 0)
		goto fail;

	return 0;
fail:
	PRT_INFO("smc smc get binary failed\n");
	return -1;
}

/* write binary to current transparent EF. */
int smc_write_binary(u8 *data, u32 file_df_id, u32 file_ef_id)
{
	u32 len;
	u32 sw2;
	struct smc_apdu_response response;

	PRT_INFO("start smc write binary\n");

	sw2 = smc_cmd_select_file(file_df_id);
	if (sw2 < 0)
		goto fail;
	sw2 = smc_cmd_select_file(file_ef_id);
	if (sw2 < 0)
		goto fail;
	response = sim_cmd_get_response(sw2);
	len = response.data[3]*16 + response.data[4];
	sw2 = smc_cmd_update_binary(data, len);
	if (sw2 < 0)
		goto fail;

	return 0;
fail:
	PRT_INFO("smc smc write binary failed\n");
	return -1;
}

/* get record in the current linear EF. */
int smc_get_record(u8 *data, u32 file_df_id, u32 file_ef_id, u32 mode, u32 id)
{
	u32 len;
	u32 sw2;
	struct smc_apdu_response response;

	PRT_INFO("start smc get record\n");

	sw2 = smc_cmd_select_file(file_df_id);
	if (sw2 < 0)
		goto fail;
	sw2 = smc_cmd_select_file(file_ef_id);
	if (sw2 < 0)
		goto fail;
	response = sim_cmd_get_response(sw2);
	len = response.data[15];
	sw2 = smc_cmd_read_record(data, len, mode, id);
	if (sw2 < 0)
		goto fail;

	return  sw2;
fail:
	PRT_INFO("smc smc get record failed\n");
	return -1;
}

/* write record in the current linear EF. */
int smc_write_record(u8 *data, u32 file_df_id, u32 file_ef_id,
		     u32 mode, u32 id)
{
	u32 len;
	u32 sw2;
	struct smc_apdu_response response;

	PRT_INFO("start smc write record\n");

	sw2 = smc_cmd_select_file(file_df_id);
	if (sw2 < 0)
		goto fail;
	sw2 = smc_cmd_select_file(file_ef_id);
	if (sw2 < 0)
		goto fail;
	response = sim_cmd_get_response(sw2);
	len = response.data[15];
	PRT_INFO("len is %x\n", len);

	sw2 = smc_cmd_update_record(data, len, mode, id);
	if (sw2 < 0)
		goto fail;

	return 0;
fail:
	PRT_INFO("smc smc write record failed\n");
	return -1;
}

/* clear record in the current linear EF, write "FF" */
int smc_clear_record(u32 file_df_id, u32 file_ef_id,
		     u32 mode, u32 id)
{
	u32 len;
	u32 sw2;
	u8 clear[MAX_LEN];
	int i;
	struct smc_apdu_response response;

	PRT_INFO("start smc clear record\n");

	sw2 = smc_cmd_select_file(file_df_id);
	if (sw2 < 0)
		goto fail;
	sw2 = smc_cmd_select_file(file_ef_id);
	if (sw2 < 0)
		goto fail;
	response = sim_cmd_get_response(sw2);
	len = response.data[15];

	clear[0] = 0;
	for (i = 1; i < len; i++)
		clear[i] = 0xff;
	sw2 = smc_cmd_update_record(clear, len, mode, id);
	if (sw2 < 0)
		goto fail;

	return 0;
fail:
	PRT_INFO("smc smc clear record failed\n");
	return -1;
}

int smc_get_iccid(void)
{
	PRT_INFO("start smc get iccid\n");
	smc_get_binary(SIM_FILE_ID_MF, SIM_FILE_ID_EF_ICCID);
	return 0;
}
EXPORT_SYMBOL_GPL(smc_get_iccid);

int smc_get_imsi(void)
{
	PRT_INFO("start smc get imsi\n");
	smc_get_binary(SIM_FILE_ID_DF_GSM, SIM_FILE_ID_EF_IMSI);
	return 0;
}
EXPORT_SYMBOL_GPL(smc_get_imsi);

int smc_get_lp(void)
{
	PRT_INFO("start smc get lp\n");
	smc_get_binary(SIM_FILE_ID_DF_GSM, SIM_FILE_ID_EF_LP);
	return 0;
}
EXPORT_SYMBOL_GPL(smc_get_lp);

int smc_write_lp(u8 *data)
{
	PRT_INFO("start smc write lp\n");
	smc_write_binary(data, SIM_FILE_ID_DF_GSM, SIM_FILE_ID_EF_LP);
	return 0;
}
EXPORT_SYMBOL_GPL(smc_write_lp);

int smc_get_contact(u32 num)
{
	u8 data[MAX_LEN];
	u32 len;

	PRT_INFO("start smc get contact\n");
	/* read current cantact id is 1, next is 2, and so on. */
	len = smc_get_record(data, SIM_FILE_ID_DF_TELECOM,
			     SIM_FILE_ID_EF_ADN, 4, num);
	print_contact(data, len);

	return 0;
}
EXPORT_SYMBOL_GPL(smc_get_contact);

int smc_write_contact(struct contact data, u32 num)
{
	u8 temp[28];
	u32 name_len = strlen(data.name);
	u32 num_len = strlen(data.telephone);
	u8 x = 0;
	u32 i = 0;

	PRT_INFO("start smc write contact\n");

	// judge input data invalid
	if (name_len == 0)
		PRT_INFO("name is empty\n");
	else if (name_len > 14)
		PRT_INFO("name len is out, max is 14 bytes\n");
	else if (num_len == 0)
		PRT_INFO("number len is empty\n");
	else if (num_len != 11)
		PRT_INFO("number len is out, max is not 11 bytes\n");
	else {
		for (i = 0; i < 11; i++) {
			if (data.telephone[i] - 48 > 10 ||
			    data.telephone[i] - 48 < 0) {
				PRT_INFO("char is illegal\n");
				return -1;
			}
		}

		i = 0;
		// set contact name, 0xff is useless
		for (; i < CONTACT_NAME_MAX; i++) {
			temp[i] = data.name[i];
			if (temp[i] == 0)
				temp[i] = 0xff;
		}

		temp[14] = 0xff;  // BCD number/SSC contents
		temp[15] = 0xff;  // TON and NPI, mobile is 0xff

		// set Dialling Number, 0xf is useless
		for (i = 0; i < CONTACT_NUMBER_MAX-1;) {
			x = ((data.telephone[i+1] - 48) << 4) +
			     (data.telephone[i] - 48);
			temp[16 + i/2] = x;
			i += 2;
		}
		temp[21] = ((data.telephone[10] - 48)) + 0xf0;
		for (i = 22; i < 26; i++)
			temp[i] = 0xff;

		temp[26] = 0xff;  // Capability/Configuration Identifier
		temp[27] = 0xff;  // Extension 1 Record Identifier

		/* write current cantact id is 1, next is 2, and so on. */
		smc_write_record(temp, SIM_FILE_ID_DF_TELECOM,
				 SIM_FILE_ID_EF_ADN, 4, num);
	}
	return 0;
}
EXPORT_SYMBOL_GPL(smc_write_contact);

int smc_clear_message(u32 num)
{
	PRT_INFO("start smc clear message\n");
	// read current cantact id is 1, next is 2, and so on.
	smc_clear_record(SIM_FILE_ID_DF_TELECOM, SIM_FILE_ID_EF_SMS, 4, num);

	return 0;
}
EXPORT_SYMBOL_GPL(smc_clear_message);

int smc_get_message(u32 num)
{
	u8 data[MAX_LEN];
	u32 len;

	PRT_INFO("start smc get message\n");

	// read current cantact id is 1, next is 2, and so on.
	len = smc_get_record(data, SIM_FILE_ID_DF_TELECOM,
			     SIM_FILE_ID_EF_SMS, 4, num);
	print_message(data, len);

	return 0;
}
EXPORT_SYMBOL_GPL(smc_get_message);

int smc_write_message(u8 *message, u32 num)
{
	// write current cantact id is 1, next is 2, and so on.
	u32 len = strlen(message);
	u32 i = 0;
	u8 temp[MESSAGE_MAX];

	PRT_INFO("smc write message\n");
	PRT_INFO("message len is %dbytes\n", len);

	// judge input data invalid
	if (len == 0)
		PRT_INFO("message is empty\n");
	else if (len > MESSAGE_MAX - 1)
		PRT_INFO("contact len is out, max is 176 bytes\n");
	else {
		temp[0] = 0;
		for (i = 0; i < strlen(message); i++)
			temp[i+1] = message[i];
		for (; i < MESSAGE_MAX - 1; i++)
			temp[i+1] = 0xff;

		smc_write_record(temp, SIM_FILE_ID_DF_TELECOM,
				 SIM_FILE_ID_EF_SMS, 4, num);
	}

	return 0;
}
EXPORT_SYMBOL_GPL(smc_write_message);

